package idPool8Bit

import (
	"encoding/binary"
	"errors"
	"math"
	"strings"
)

const (
	maxSubscript    int8 = 2 << 5
	max8BitWorkID        = 9
	max8BitWorkSize      = 10
	max8BitIDSize        = 100000000
	// max8BitID                       = 99999999
	maxUint64                uint64 = math.MaxUint64
	maxCurrentWork8BitIDSize        = max8BitIDSize / max8BitWorkSize
	subscriptLen                    = maxCurrentWork8BitIDSize / uint64(maxSubscript)
	blockSize                       = 8
)

type IDPool interface {
	// NextID get next id
	NextID() (id uint64, err error)

	Reset()

	Stop() error
}

// idPool is Concurrency is not safe
// you can add mutex if you need the safe idPool
// This method may participate in concurrent operations,
// but in order to reduce the possibility of repeated locking due to other race conditions,
// please actively lock when you need concurrency safety.
type idPool struct {
	fileName   string
	poolRecord *idPoolRecord
	mmapData   []byte
}

func (p *idPool) NextID() (uint64, error) {

	// pre get id
	nextBlock, nextIterateNum, id, newBlock, nextIterate, err := p.poolRecord.getNextID()
	if err != nil {
		return 0, err
	}

	// wal write record log
	if err = p.writeWalLog(nextBlock, nextIterateNum, newBlock, nextIterate); err != nil {
		return 0, err
	}

	if err = p.poolRecord.move(nextBlock, nextIterateNum, newBlock); err != nil {
		return 0, err
	}

	return id, nil
}

func (p *idPool) writeWalLog(nextBlock uint32, nextIterateNum int8, newBlock Block, nextIterate bool) error {
	// Calculate write position
	writeOffset := IDPoolPrefixOffsetSize + nextBlock*blockSize
	if int(writeOffset+blockSize) > len(p.mmapData) {
		return errors.New("write offset out of bounds")
	}

	if nextIterate {
		p.mmapData[1] = byte(nextIterateNum)
	}
	binary.LittleEndian.PutUint32(p.mmapData[2:], nextBlock)
	binary.LittleEndian.PutUint64(p.mmapData[6:], p.poolRecord.Consume)
	binary.LittleEndian.PutUint64(p.mmapData[writeOffset:], uint64(newBlock))
	return nil
}

func (p *idPool) readLog(nextBlock uint32) (uint64, error) {
	readOffset := IDPoolPrefixOffsetSize + nextBlock*blockSize
	if int(readOffset+blockSize) > len(p.mmapData) {
		return 0, errors.New("read offset out of bounds")
	}

	value := binary.LittleEndian.Uint64(p.mmapData[readOffset:])
	return value, nil
}

func (p *idPool) Stop() error {
	p.poolRecord.Blocks = nil
	return munmapFile(p.mmapData)
}

// Reset Please stop first and then execute it
func (p *idPool) Reset() {
	p.poolRecord = &idPoolRecord{
		WorkID:       0,
		IterateNum:   0,
		CurrentBlock: 0,
		Consume:      0,
		Blocks:       nil,
	}
	p.fileName = ""
	p.mmapData = nil
}

// NewIDPool new 8bit id pool
func NewIDPool(workID uint8, storePath string, newPool bool) (IDPool, error) {
	if workID > max8BitWorkID {
		return nil, errors.New("8 bit idPoolRecord WorkID must range 0-9")
	}

	var (
		mmapData []byte
		err      error
	)
	fileName := strings.Join([]string{storePath, ".log"}, "")

	record := new(idPoolRecord)
	record.WorkID = workID
	record.Blocks = make([]Block, subscriptLen)

	if newPool {
		if mmapData, err = record.initWalLogFile(fileName); err != nil {
			return nil, err
		}

	} else {
		if mmapData, err = record.recoverFormWalLog(fileName); err != nil {
			return nil, err
		}

	}

	return &idPool{
		fileName:   fileName,
		poolRecord: record,
		mmapData:   mmapData,
	}, nil
}
